// 
//  Copyright (C) 2009 Jacek Trubłajewicz
// 
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//  
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 

using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
using Mono.Unix;
using HAKGERSoft;
using NLog;

namespace wxGadu
{
	/// <summary>
	/// Proxy between <c>Gadu.Users</c> and
	/// <c>ContactListCtrl</c>. Keeps information which
	/// contact is on which position in widget list
	/// </summary>
	public class ContactListProxy
	{
		/// <summary>
		/// Helper class to store information about UIN real position on 
		/// <c>ContactListCtrl</c>.
		/// </summary>
		public class UserPos
		{
			/// <summary>
			/// Keeps contact UIN.
			/// </summary>
			public int uin;
			/// <summary>
			/// Position of main (name) item on <c>ContactListCtrl</c>
			/// </summary>
			public int main;
			/// <summary>
			/// Indicates user description 
			/// </summary>
			/// <remarks>
			/// True if user has description, false if user doesn't have description
			/// </remarks> 
			public bool desc;
			
			/// <summary>
			/// Ctor
			/// </summary>
			public UserPos()
			{
				uin = -1;
				main = -1;
				desc = false;
			}
			
			/// <summary>
			/// Conversion operator
			/// </summary>
			/// <returns>
			/// A <see cref="System.String"/>
			/// </returns>
			public override string ToString()
			{
				return String.Format("UIN: {0}, main: {1}, desc: {2}", this.uin, this.main, this.desc);
			}
			
		}
		
		#region data members
		private GGUsers users;
		private Dictionary<int, ContactInfo> contacts;
		private MainFrame mainFrame;
		private List<UserPos> positions;
		private Mutex positionsMutex;
#if LOG
		private Logger logger = LogManager.GetCurrentClassLogger();
#endif
		private wx.Font descFont;
		private wx.Font mainFont;
		
		#endregion
			
		/// <summary>
		/// Ctor
		/// </summary>
		/// <param name="users">
		/// A <see cref="GGUsers"/>
		/// contact list in network lib
		/// </param>
		/// <param name="contacts">
		/// A <see cref="Dictionary"/>
		/// contact list in <c>ContactsManager</c>
		/// </param>
		/// <param name="mainFrame">
		/// A <see cref="MainFrame"/>
		/// main application window
		/// </param>
		public ContactListProxy(GGUsers users, Dictionary<int, ContactInfo> contacts, MainFrame mainFrame)
		{
			this.mainFrame = mainFrame;
			PrepareFonts();
			if (mainFont is wx.Font)
				mainFrame.TopWindow.ContactsListCtrl.Font = mainFont;
			
			this.users = users;
			this.contacts = contacts;
			positions = new List<UserPos>();
			positionsMutex = new Mutex();
		}
		
		/// <summary>
		/// Adds contact into internal <c>ContactListProxy</c> list
		/// and <c>ContactListCtrl</c> widget.
		/// </summary>
		/// <param name="user">
		/// A <see cref="GGUser"/>
		/// contact (user) that should be added
		/// </param>
		/// <remarks>
		/// Added contact will be inserted at the end of the list.
		/// </remarks>
		public void AddContact(GGUser user)
		{
			positionsMutex.WaitOne();
#if LOG 
			logger.Trace(String.Format(Catalog.GetString("Adding UIN: {0} to ContactListProxy"), user.GGNumber));
#endif
			//Thread.Sleep(1000); // fake lag to check synchronization
			// add new to the end of the list, it will be sorted when GG server send back status packet
			InsertContact(positions.Count, user);
			
			positionsMutex.ReleaseMutex();
		}
		
		/// <summary>
		/// Removes user (contact) form internal ContactListProxy list
		/// and from ContactListCtrl widget. 
		/// </summary>
		/// <param name="uin">
		/// Uin of contact which should be deleted
		/// A <see cref="System.Int32"/>
		/// </param>
		public void DeleteContact(int uin)
		{
			positionsMutex.WaitOne();
			{
				// determine contact position in positions list
				// first we try the old position from ContactManager
				int oldPos = contacts[uin].Position;
				if (oldPos != -1)
				{
					if (uin == GetUin(oldPos))
					{
						// oldPosition is the right one
					}
					else
					{	// we need to look through the whole list to find position of user
						oldPos = GetPosition(uin);
					}
				}
				else
				{	// we need to look through the whole list to find position of user
					oldPos = GetPosition(uin);						
				}
				
				// now we know old position on contactProxy list so we can update this list
				// and update ContactListCtrl by removing item
				RemoveContact(oldPos, uin);			
			}
			positionsMutex.ReleaseMutex();
		}
		
		/// <summary>
		/// Deletes or updates real position, status and description of user
		/// on ContactListCtrl.
		/// </summary>
		/// <param name="newPos">
		/// A <see cref="System.Int32"/>
		/// Current user position on Gadu.Users list.
		/// </param>
		/// <param name="user">
		/// A <see cref="GGUser"/>
		/// User (contact) which need to be updated.
		/// </param>
		/// <remarks>
		/// If newPos value is -1, user will be deleted from list 
		/// and form ContactListCtrl.
		/// </remarks>
		public void UpdateContact(int newPos, GGUser user)
		{			
			positionsMutex.WaitOne();
			
#if LOG 
			logger.Trace(String.Format(Catalog.GetString("Updating UIN: {0}, new position: {1}"), user.GGNumber, newPos));
#endif
			
			// determine did contact position on Gadu.Users has changed
			if (user.GGNumber != GetUin(newPos))
			{	// position did change
				
				// first we try the old position from ContactManager
				int oldPos = contacts[user.GGNumber].Position;
				if (oldPos != -1)
				{
					if (user.GGNumber == GetUin(oldPos))
					{
						// oldPosition is the right one
					}
					else
					{	// we need to look through the whole list to find position of user
						oldPos = GetPosition(user.GGNumber);
					}
				}
				else
				{	// we need to look through the whole list to find position of user
					oldPos = GetPosition(user.GGNumber);						
				}
				
				// now we know old position on contactProxy list so we can update this list
				// and update ContactListCtrl by removing item
				RemoveContact(oldPos, user.GGNumber);
				if (newPos != -1) // we add contact again only if it's not intended for deletions
					InsertContact(newPos, user);

			}
			else
			{	// position didn't change
				
				// updating description
				if (user.Description != string.Empty)
				{	// user has description
					if (positions[newPos].desc) 
					{	// and he had it earlier
						//mainFrame.TopWindow.ContactsListCtrl.SetItemText(positions[newPos].main + 1, user.Description);
						wx.ListItem descItem = new wx.ListItem();
						descItem.Id = positions[newPos].main + 1;
						descItem.Text = user.Description;
						descItem.Font = descFont;
						mainFrame.TopWindow.ContactsListCtrl.SetItem(descItem);
						contacts[user.GGNumber].Description = user.Description;
					}
					else
					{	// he didn't have description earlier
						positions[newPos].desc = true;						
//						mainFrame.TopWindow.ContactsListCtrl.InsertItem(
//						                                                positions[newPos].main + 1,
//						                                                user.Description
//						                                                );
						wx.ListItem descItem = new wx.ListItem(user.Description, 0);
						descItem.Font = descFont;
						descItem.Id = positions[newPos].main + 1;
				
						mainFrame.TopWindow.ContactsListCtrl.InsertItem(descItem);
						contacts[user.GGNumber].Description = user.Description;
						// update real positions of the rest of the list
						for (int i = newPos + 1; i < positions.Count; i++)
							positions[i].main += 1;
					}
				}
				else
				{
					if (!positions[newPos].desc) 
					{
						// user didn't have description earlier 
						// we don't need to do anything
					}
					else
					{	// user had description earlier, we need to delete and update
						positions[newPos].desc = false;
						mainFrame.TopWindow.ContactsListCtrl.DeleteItem(positions[newPos].main + 1);
						contacts[user.GGNumber].Description = user.Description;
						// update real positions of the rest of the list
						for (int i = newPos + 1; i < positions.Count; i++)
							positions[i].main -= 1;
					}
				}
				
				// updating main (name) field
				mainFrame.TopWindow.ContactsListCtrl.SetItemImage(
	                                                positions[newPos].main,                             
	                                                ImageManager.ImageIndexFromStatus(user.GGStatus, user.Description),
				                                 	ImageManager.ImageIndexFromStatus(user.GGStatus, user.Description)
	                                                );

			}
			
			// if contact info is visible on MainFrame.BottomWindow update it
			if (mainFrame.BottomWindow.VisibleUin == user.GGNumber)
			{
				mainFrame.BottomWindow.DisplayContactInfo(user.GGNumber);
			}
			
			positionsMutex.ReleaseMutex();
			//return positions[newPos].main;
		}
		
		/// <summary>
		/// Removes user (contact) form internal ContactListProxy list
		/// and from ContactListCtrl widget. 
		/// </summary>
		/// <param name="position">
		/// A <see cref="System.Int32"/>
		/// User (contact) positon on internal ContactListProxy list.
		/// </param>
		/// <param name="uin">
		/// A <see cref="System.Int32"/>
		/// Uin of user which is about to be removed.
		/// </param>
		/// <exception cref="ArgumentException">
		/// Thrown when given uin is different than uin of item on 
		/// specific position on internal ContactListProxy list. 
		/// </exception>
		private void RemoveContact(int position, int uin)
		{
			//positionsMutex.WaitOne();
			
			if (positions[position].uin != uin)
				throw new ArgumentException("Uin or position on list is wrong");

			// remove item from ContactListCtrl
			if (positions[position].desc)
				mainFrame.TopWindow.ContactsListCtrl.DeleteItem(positions[position].main + 1);
			mainFrame.TopWindow.ContactsListCtrl.DeleteItem(positions[position].main);
			
			// calculate how much real positions of the rest of the list will change
			int difference = 1;
			if (positions[position].desc)
				difference = 2;
		
			// update real position of the rest of the list
			for (int i = position +1; i < positions.Count; i++)
				positions[i].main -= difference;		
			
			// remove item from positions list
			positions.RemoveAt(position);
			
			//positionsMutex.ReleaseMutex();			
		}
		
//		/// <summary>
//		/// Removes user (contact) form internal ContactListProxy list
//		/// and from ContactListCtrl widget. 
//		/// </summary>
//		/// <param name="uin">
//		/// A <see cref="System.Int32"/>
//		/// UIN of contact that should be deleted from the list
//		/// </param>
//		private void RemoveContact(int uin)
//		{
//			positionsMutex.WaitOne();
//			
//			int pos;
//			try
//			{
//				pos = GetPosition(uin);
//				RemoveContact(pos, uin);		
//			}
//			catch (ArgumentException e)
//			{
//#if LOG
//				logger.ErrorException(String.Format(Catalog.GetString("User with UIN: {0} not found on the internal ContactListProxy list"), uin), e);
//#endif
//				return;
//			}
//			
//#if LOG
//			logger.Trace(String.Format(Catalog.GetString("Contact with UIN:{0} deleted from ContactListProxy list"), uin));
//#endif
//
//			positionsMutex.ReleaseMutex();
//		}
		
		/// <summary>
		/// Inserts user (contact) into internal ContactListProxy list and
		/// into ContactListCtrl widget.
		/// </summary>
		/// <param name="newPos">
		/// A <see cref="System.Int32"/>
		/// User position on Gadu.Users list.
		/// </param>
		/// <param name="user">
		/// A <see cref="GGUser"/>
		/// User (contact) which is about to be added.
		/// </param>
		/// <remarks>
		/// Added contact will be inserted at the end of the list.
		/// </remarks>
		private void InsertContact(int newPos, GGUser user)
		{		
			// count contact real position on ContactListCtrl
			if (positions.Count == 0)		// list is empy so position will be 0
			{
				UserPos p = new UserPos();
				p.uin = user.GGNumber;
				p.main = 0; // because list is empty this will be 0
				if (user.Description != string.Empty)
					p.desc = true;
				// now insert this to the positions list
				positions.Insert(newPos, p);
			}
			else // count real list position by checking position of previous item
			{
				UserPos p = new UserPos();
				p.uin = user.GGNumber;
				if (newPos > 0)
				{
					UserPos prevP = positions[newPos - 1];
					p.main = prevP.desc == false ? prevP.main + 1 : prevP.main + 2;
				}
				else
				{
					p.main = 0;
				}
				
				int difference = 1; // how much rest of list will be moved
				if (user.Description != string.Empty)
				{
					p.desc = true;
					difference = 2;
				}
				// now we insert this to the positions list
				positions.Insert(newPos, p);
				
				// and update position of the rest of items on the list
				// by adding difference value
				for(int i = newPos+1; i < positions.Count; i++)
					positions[i].main += difference;
			}		
			
			// positions list is updated now so we can add item to ContactListCtrl
			mainFrame.TopWindow.ContactsListCtrl.InsertItem(
			                                                positions[newPos].main, 
			                                                contacts[user.GGNumber].Name,
			                                                ImageManager.ImageIndexFromStatus(user.GGStatus, user.Description)
			                                                );
			
			if (positions[newPos].desc)
			{
//				mainFrame.TopWindow.ContactsListCtrl.InsertItem(
//				                                                positions[newPos].main + 1,
//				                                                user.Description
//				                                            	
//				                                                );
				wx.ListItem descItem = new wx.ListItem(user.Description, 0);
				descItem.Font = descFont;
				descItem.Id = positions[newPos].main + 1;
				
				mainFrame.TopWindow.ContactsListCtrl.InsertItem(descItem);
				
			}
			
			// update ContactManger.Contacts list
			contacts[user.GGNumber].Position = newPos;
			//contacts[user.GGNumber].Status = user.GGStatus;
			contacts[user.GGNumber].Description = user.Description;
			
		}
		
			
		/// <summary>
		/// Returns user UIN.
		/// </summary>
		/// <param name="position">
		/// A <see cref="System.Int32"/>
		/// User position on internal <c>ContactListProxy.positions</c> list.
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// User UIN.
		/// </returns>
		/// <exception cref="ArgumentOutOfRangeException">
		/// When <c>position</c> has invalid value.
		/// </exception>
		public int GetUin(int position)
		{
			if (position > positions.Count -1)
				throw new ArgumentOutOfRangeException(String.Format("Invalid position = {0} but positions.Count = {1}", position, positions.Count));
			else
				if (position < 0)
					return -1;
				else
					return positions[position].uin;
		}
		
		/// <summary>
		/// Returns position of user on internal ContactListProxy list. 
		/// </summary>
		/// <param name="uin">
		/// A <see cref="System.Int32"/>
		/// User UIN.
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// User postion on internal ContactListProxy list.
		/// </returns>
		/// <exception cref="ArgumentOutOfRangeException">
		/// When <c>uin</c> value is below 0 
		/// </exception>
		/// <exception cref="ArgumentException">
		/// When user with passed <c>uin</c> value is not found on
		/// the internal ContactListProxy list.
		/// </exception>
		public int GetPosition(int uin)
		{
			if (uin < 1)
				throw new ArgumentOutOfRangeException("Uin should be grater or equal to 1");
			
			for(int i = 0; i < positions.Count; i++)
			{
				if (positions[i].uin == uin)
					return i;
			}
			
			throw new ArgumentException("Uin not found, this shouldn't happen!");
		}
		
		/// <summary>
		/// Used to get contact UIN form real position on <c>ContactListCtrl</c>
		/// </summary>
		/// <param name="realPosition">
		/// A <see cref="System.Int32"/>
		/// real position on <c>ContactListCtrl</c>
		/// </param>
		/// <returns>
		/// A <see cref="UserPos"/>
		/// object containg contact UIN
		/// </returns>
		public UserPos GetUserPos(int realPosition)
		{
			positionsMutex.WaitOne();
			
			if (realPosition > mainFrame.TopWindow.ContactsListCtrl.ItemCount || realPosition < 0)
				throw new ArgumentOutOfRangeException(
				                                      String.Format(
				                                                    "realPosition value is {0}, must be <0, {0}>", 
				                                                    mainFrame.TopWindow.ContactsListCtrl.ItemCount
				                                                    )
				                                      );
			
			int i, j = 0;
			for (i = 0; i < positions.Count; i++)
			{
				if (j == realPosition)
					break;
				j++;
				if (positions[i].desc)
				{
					if (j == realPosition)
						break;
					else
						j++;
				}
			}
			
			UserPos up = new UserPos();
			up.main = positions[i].main;
			up.uin = positions[i].uin;
			up.desc = positions[i].desc;
			
			positionsMutex.ReleaseMutex();
			
			return up;			
		}
		
		/// <summary>
		/// Clears both internal <c>positions</c> list
		/// and <c>ContactListCtrl</c>
		/// </summary>
		public void ClearList()
		{
			positionsMutex.WaitOne();
			
			mainFrame.TopWindow.ContactsListCtrl.DeleteAllItems();
			positions.Clear();
			
			positionsMutex.ReleaseMutex();
		}
		
		public override string ToString()
		{
			foreach(UserPos user in positions)
			{
				System.Console.WriteLine("UIN: {0}\n main pos: {1}\n desc pos: {2}\n---", user.uin, user.main, user.desc);
			}
			return string.Format("[ContactListProxy]");
		}
		
		//// <value>
		/// Items count on internal ContactListProxy list.
		/// </value>
		public int Count
		{
			get { return positions.Count; }
		}
		
		private void PrepareFonts()
		{	
			if (mainFrame.AppCfg.UserConf.ContactListFont is Font)
			{
				mainFont = (wx.Font) mainFrame.AppCfg.UserConf.ContactListFont;
				descFont = (wx.Font) mainFrame.AppCfg.UserConf.ContactListFont;
			}
			else
			{
				mainFont = wx.SystemSettings.GetFont(wx.SystemFont.wxSYS_DEFAULT_GUI_FONT);
				descFont = wx.SystemSettings.GetFont(wx.SystemFont.wxSYS_DEFAULT_GUI_FONT);
			}
			descFont.PointSize -= 1;
		}
		
		public void UpdateContactListCtrlFonts(object sender, wx.Event evt)
		{
			positionsMutex.WaitOne();
			
			PrepareFonts();
			// setup global font
			if (mainFont is wx.Font)
				mainFrame.TopWindow.ContactsListCtrl.Font = mainFont;
			
			// setup font for each description item
			foreach (UserPos up in positions)
			{
				if (up.desc)
				{
					wx.ListItem descItem = new wx.ListItem();
					descItem.Id = up.main + 1;
					mainFrame.TopWindow.ContactsListCtrl.GetItem(ref descItem);
					descItem.Font = descFont;
					mainFrame.TopWindow.ContactsListCtrl.SetItem(descItem);
				}
			}
			
			positionsMutex.ReleaseMutex();			
		}
	}
}
